﻿/**
 * Copyright (C) 2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Internal.Util;
using NGuice.Inject.Spi;

namespace NGuice.Inject.Internal
{
    //complete!
    /// <summary>
    /// 处理<code>Binder.requestInjection和Binder.requestStaticInjection</code>命令
    /// </summary>
    internal sealed class InjectionRequestProcessor : AbstractProcessor
    {
        private readonly IList<StaticInjection> staticInjections = Lists.NewArrayList<StaticInjection>();
        private readonly Initializer initializer;

        internal InjectionRequestProcessor(Errors errors, Initializer initializer)
            : base(errors)
        {
            this.initializer = initializer;
        }

        public override bool Visit(StaticInjectionRequest request)
        {
            staticInjections.Add(new StaticInjection(this, injector, request));
            return true;
        }

        public override bool Visit(InjectionRequest request)
        {
            ISet<InjectionPoint> injectionPoints;
            try
            {
                injectionPoints = request.GetInjectionPoints();
            }
            catch (ConfigurationException e)
            {
                errors.Merge(e.ErrorMessages);
                injectionPoints = (ISet<InjectionPoint>)e.GetPartialValue();
            }

            initializer.RequestInjection(injector, request.Instance, request.Source, injectionPoints);
            return true;
        }

        internal void Validate()
        {
            foreach (StaticInjection staticInjection in staticInjections)
            {
                staticInjection.Validate();
            }
        }

        internal void InjectMembers()
        {
            /*
             * TODO: If you request both a parent class and one of its
             * subclasses, the parent class's static members will be
             * injected twice.
             */
            foreach (StaticInjection staticInjection in staticInjections)
            {
                staticInjection.InjectMembers();
            }
        }
    }

    /// <summary>
    /// 一个请求的静态注入
    /// </summary>
    internal class StaticInjection
    {
        internal readonly InjectorImpl injector;
        internal readonly object source;
        internal readonly StaticInjectionRequest request;
        internal ImmutableList<SingleMemberInjector> memberInjectors;
        internal InjectionRequestProcessor outer;

        public StaticInjection(InjectionRequestProcessor outer, InjectorImpl injector, StaticInjectionRequest request)
        {
            this.outer = outer;
            this.injector = injector;
            this.source = request.Source;
            this.request = request;
        }

        internal void Validate()
        {
            Errors errorsForMember = outer.errors.WithSource(source);
            ISet<InjectionPoint> injectionPoints;
            try
            {
                injectionPoints = request.GetInjectionPoints();
            }
            catch (ConfigurationException e)
            {
                outer.errors.Merge(e.ErrorMessages);
                injectionPoints = (ISet<InjectionPoint>)e.GetPartialValue();
            }
            memberInjectors = injector.membersInjectorStore.GetInjectors(injectionPoints, errorsForMember);
        }

        internal void InjectMembers()
        {
            try
            {
                injector.CallInContext(new StaticInjectionContextualCallable(this));
            }
            catch (ErrorsException e)
            {
                throw new AssertionError();
            }
        }

        private class StaticInjectionContextualCallable : ContextualCallable<AsyncVoid>
        {
            private StaticInjection injection;

            public StaticInjectionContextualCallable(StaticInjection injection)
            {
                this.injection = injection;
            }

            public AsyncVoid Call(InternalContext context)
            {
                foreach (SingleMemberInjector memberInjector in injection.memberInjectors)
                {
                    // Run injections if we're not in tool stage (ie, PRODUCTION or DEV),
                    // or if we are in tool stage and the injection point is toolable.
                    if (injection.injector.options.stage != Stage.TOOL || memberInjector.GetInjectionPoint().IsToolable)
                    {
                        memberInjector.Inject(injection.outer.errors, context, null);
                    }
                }
                return null;
            }
        }
    }
}
